﻿namespace DKE_Kinect.Entwicklung.Grafik
{
	/// <summary>
	/// Repräsentiert einen Übergangseffekt.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public abstract class TransitionEffect<T>
	{
		/// <summary>
		/// Stellt das Original dar.
		/// </summary>
		protected T pOrig;
		/// <summary>
		/// Stellt das Ziel des Übergangs dar.
		/// </summary>
		protected T pDest;

		/// <summary>
		/// Bisher verstrichener Wert (i.d.R. Zeit).
		/// </summary>
		protected long pCurrValue;
		/// <summary>
		/// Zu erreichender Zielwert (i.d.R. Zeit).
		/// </summary>
		protected readonly long pDuration;
		/// <summary>
		/// Anteil des verstrichenen Wertes gegenüber dem Endwert.
		/// </summary>
		private float pPercent;

		/// <summary>
		/// Gibt an, ob der Übergang fertiggestellt wurde.
		/// </summary>
		private bool pFinished;
		/// <summary>
		/// Gibt an, ob der Übergang gestartet und noch nicht beendet wurde.
		/// </summary>
		private bool pWorking;
		/// <summary>
		/// Wurde ein Revert angefordert.
		/// </summary>
		private bool pRevert;
		/// <summary>
		/// Gibt die Art der Werteinterpolation an.
		/// </summary>
		private TransitionInterpolation pInterpolation;

		/// <summary>
		/// Erstellt eine neue Instanz dieser Klasse.
		/// </summary>
		/// <param name="Orig">Stellt das Original dar.</param>
		/// <param name="Dest">Stellt das Ziel dar.</param>
		/// <param name="Duration">Stellt den zu erreichenden Zielwert dar.</param>
		public TransitionEffect (T Orig, T Dest, long Duration)
		{
			if (Duration <= 0)
				throw new System.ArgumentOutOfRangeException();

			this.pOrig = Orig;
			this.pDest = Dest;

			this.pCurrValue = 0;
			this.pDuration = Duration;
			this.pPercent = 0f;
			this.pRevert = false;

			this.pWorking = false;
			this.pFinished = false;

			this.pInterpolation = TransitionInterpolation.Linear;
		}

		/// <summary>
		/// Gibt das Original an oder legt dieses (auch während eines Verlaufes) fest.
		/// </summary>
		public T Original { get { return this.pOrig; } set { this.pOrig = value; } }
		/// <summary>
		/// Gibt den Zielwert an oder legt diesen (auch während eines Verlaufes) fest.
		/// </summary>
		public T Destination { get { return this.pDest; } set { this.pDest = value; } }

		/// <summary>
		/// Gibt den Typ der Interpolation an.
		/// </summary>
		public TransitionInterpolation Interpolation { get { return this.pInterpolation; } set { this.pInterpolation = value; } }

		/// <summary>
		/// Gibt an, ob der Verlauf in Standardrichtung verläuft oder in umgekehrter.
		/// </summary>
		public bool Reverted { get { return this.pRevert; } }

		/// <summary>
		/// Gibt den bisher verstrichenen Wert an (i.d.R. Zeit).
		/// </summary>
		public long CurrentValue { get { return this.pCurrValue; } }
		/// <summary>
		/// Gibt den zu erreichenden Zielwert an (i.d.R. Zeit).
		/// </summary>
		public long Duration { get { return this.pDuration; } }
		/// <summary>
		/// Gibt den Anteil des verstrichenen Wertes gegenüber dem Endwert an.
		/// </summary>
		protected float Percent { get { return this.pPercent; } }

		/// <summary>
		/// Gibt an, ob der Übergang im nichtarbeitenden Zustand ist.
		/// </summary>
		public bool Idle { get { return !this.pWorking; } }
		/// <summary>
		/// Gibt an, ob der Übergang gestartet und nicht beendet wurde.
		/// </summary>
		public bool Working { get { return this.pWorking; } }
		/// <summary>
		/// Gibt an, ob der Übergang beendet ist.
		/// </summary>
		public bool Finished { get { return this.pFinished; } }

		/// <summary>
		/// Löst das Ereignis TransitionFinished aus.
		/// </summary>
		/// <param name="e">Ereignisparameter</param>
		protected void OnFinished (System.EventArgs e)
		{
			if (this.TransitionFinished != null)
				this.TransitionFinished(this, e);
		}
		///// <summary>
		///// Löst das Ereignis TransitionFinishing aus.
		///// </summary>
		///// <param name="e"></param>
		//protected void OnFinishing (System.EventArgs e)
		//{
		//  if (this.TransitionFinishing != null)
		//    this.TransitionFinishing(this, e);
		//}

		/// <summary>
		/// Bereitet den Übergang in normaler Richtung vor.
		/// </summary>
		public void BeginFromFront ()
		{
			this.pCurrValue = 0;
			this.pPercent = 0f;

			this.pWorking = true;
			this.pFinished = false;

			this.ResetMerged(this.pOrig);
			this.pRevert = false;
		}
		/// <summary>
		/// Bereitet den Übergang in derzeitiger Richtung vor, d.h. das Original stellt das Ziel dar und das Ziel das Original.
		/// </summary>
		/// 
		public void BeginFromBack ()
		{
			this.pCurrValue = this.pDuration;
			this.pPercent = 1f;

			this.pWorking = true;
			this.pFinished = false;

			this.ResetMerged(this.pDest);
			this.pRevert = true;
		}
		/// <summary>
		/// Setzt den aktuellen Übergang mit aktueller Richtung zurück.
		/// </summary>
		public void Begin ()
		{
			if (this.pRevert)
				this.BeginFromBack();
			else
				this.BeginFromFront();
		}

		/// <summary>
		/// Setzt den Übergang auf den alten zuvor gesetzten Wert zurück.
		/// </summary>
		public void Reset ()
		{
			this.pCurrValue = (this.pRevert) ? this.pDuration : 0;
			this.pPercent = this.pCurrValue / this.pDuration;
			this.ResetMerged((this.pRevert) ? this.pDest : this.pOrig);
			this.pWorking = false;
			this.pFinished = false;
		}

		/// <summary>
		/// Beendet vorzeitig den Übergang, sofern er gestartet wurde.
		/// </summary>
		public void Finish ()
		{
			if (this.pWorking)
			{
				this.pCurrValue = (this.pRevert) ? 0 : this.pDuration;
				this.pPercent = this.pCurrValue / this.pDuration;
				this.ResetMerged((this.pRevert) ? this.pOrig : this.pDest);
				this.pWorking = false;
				this.pFinished = false;
			}
		}

		/// <summary>
		/// Der Übergang wird gegenüber dem derzeitigen Übergang in umgekehrter Richtung fortgesetzt.
		/// </summary>
		public void Revert ()
		{
			this.pRevert = !this.pRevert;
		}

		/// <summary>
		/// Lässt einen bestimmten Wert verstreichen.
		/// </summary>
		/// <param name="Elapsed">Vertrichender Wert.</param>
		public void Update (long Elapsed)
		{
			// Prüfen, ob der Übergang abgeschlossen ist:
			if ((this.pFinished) || (!this.pWorking))
				return;

			// Prüfen, ob ein umgekehrter Übergang angefordert wurde:
			if (this.pRevert)
			{
				this.pCurrValue -= Elapsed;

				if (this.pCurrValue <= 0)
				{
					this.pCurrValue = 0;
					this.pWorking = false;
					this.pFinished = true;
					this.OnFinished(System.EventArgs.Empty);
				}
			}
			else
			{
				this.pCurrValue += Elapsed;

				if (this.pCurrValue >= this.pDuration)
				{
					this.pCurrValue = this.pDuration;
					this.pWorking = false;
					this.pFinished = true;
					this.OnFinished(System.EventArgs.Empty);
				}
			}

			this.pPercent = (float)this.pCurrValue / this.pDuration;
			this.UpdateValues(this.pInterpolation);
		}

		///// <summary>
		///// Tritt ein, bevor die Update - Methode das letze Mal aufgerufen wird.
		///// Das Finishing kann auch durch ein Revert() umgangen werden.
		///// </summary>
		//public event System.EventHandler TransitionFinishing;
		/// <summary>
		/// Tritt ein, wenn der Übergang beendet wird.
		/// </summary>
		public event System.EventHandler TransitionFinished;

		/// <summary>
		/// Gibt den bisher erreichten Übergang zurück.
		/// </summary>
		public abstract T Merged { get; }
		/// <summary>
		/// Setzt das Übergangs-Objekt zurück.
		/// </summary>
		/// <param name="Orig"></param>
		protected abstract void ResetMerged (T Orig);
		/// <summary>
		/// Stellt die Methode dar, die den eigentlichen Übergang repräsentiert und die Daten aktualisiert.
		/// Diese Methode wird von Update bzw. UpdateRevert aufgerufen.
		/// </summary>
		protected abstract void UpdateValues (TransitionInterpolation InPol);
	}

	/// <summary>
	/// Beschreibt eine Auflistung von möglichen Interpolationen für einen Übergang.
	/// </summary>
	public enum TransitionInterpolation
	{
		/// <summary>
		/// Lineare Interpolation.
		/// </summary>
		Linear = 0,
		/// <summary>
		/// Quadratische Interpolation.
		/// </summary>
		Quadratic = 1,
		/// <summary>
		/// Kubische Interpolation.
		/// </summary>
		Cubic = 2,
		/// <summary>
		/// Interpolation nach Quadratwurzel.
		/// </summary>
		SquareRoot = 3
	}
}
